#include "stdafx.h"
#include "Builtin.h"
#include "Environment.h"
#include <algorithm>
#include <iterator>
#include <functional>
#include <cstdlib>
#include <cmath>
#include <boost/lexical_cast.hpp>
#include "ConsoleManager.h"
#include "Support.h"
#include "GlobalVariables.h"
#include "Errors.h"

namespace MiniBasic
{
    namespace Builtin
    {
        using namespace Errors;

        template<class OutputIterator>
        bool UnpackArgs(const std::string& args, OutputIterator resultOutput)
        {
            std::vector<std::string> parts;
            parts.reserve(3);
            ExplodeDelimitedString(args, ';', std::back_inserter(parts));
            try
            {
                std::transform(parts.begin(), parts.end(), resultOutput, &boost::lexical_cast<mbint, std::string>);
                return true;
            }
            catch(const boost::bad_lexical_cast&)
            {
                return false; // just exit, the caller can do validation on the number of parameters, etc
            }
        }

        mbint Poke(Environment* env, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<mbint> integers;
            if(UnpackArgs(arguments, std::back_inserter(integers)) && (integers.size() >= 2))
            {
                LARGE_INTEGER li = {integers[0]};
                MemoryArea& userMemory = env->GetUserMemoryArea();
                std::vector<BYTE> values(integers.begin() + 1, integers.end());
                //values.reserve(integers.size());
                //std::transform(integers.begin() + 1, integers.end(), std::back_inserter(values), boost::lambda::constant(0xFF) & boost::lambda::_1);
                
                return SUCCEEDED(userMemory.Seek(li, STREAM_SEEK_SET, NULL)) && 
                    SUCCEEDED(userMemory.Write(&values[0], values.size(), NULL));
            }
            else
            {
                error = INVALID_PARAMETER;
            }
            return 0;
        }

        mbint Peek(Environment* env, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<mbint> ints;
            if(UnpackArgs(arguments, std::back_inserter(ints)) && (ints.size() == 1))
            {
                LARGE_INTEGER li = {ints[0]};
                MemoryArea& userMemory = env->GetUserMemoryArea();
                BYTE byte = 0;
                if(SUCCEEDED(userMemory.Seek(li, STREAM_SEEK_SET, NULL)))
                {
                    userMemory.Read(&byte, 1, NULL);
                    return byte;
                }
                else
                {
                    error = INVALID_PARAMETER;
                }
            }
            else
            {
                error = INVALID_PARAMETER;
            }
            return 0;
        }

        mbint Locate(Environment* env, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<mbint> ints;
            if(UnpackArgs(arguments, std::back_inserter(ints)) && (ints.size() >= 2))
            {
                mbint ret = 0;
                COORD coord = {static_cast<SHORT>(ints[0]), static_cast<SHORT>(ints[1])};
                ConsoleManager& conMan = env->GetConsole();
                ret = conMan.MoveCursor(coord);
                if(ret)
                {
                    if(ints.size() == 3)
                    {
                        ret = conMan.ToggleCursor(!!ints[2]);
                    }
                }
                else
                {
                    error = INTERNAL_FUNCTION_FAILURE;
                    ret = static_cast<mbint>(GetLastError());
                }
                return ret;
            }
            else
            {
                error = INVALID_PARAMETER;
            }
            return 0;
        }

        mbint Window(Environment* env, const std::string& arguments, ERROR_CONDITION& error)
        {
            ConsoleManager& conMan = env->GetConsole();
            std::string potentialEdit = arguments.substr(0, 4);
            if(potentialEdit == "EDIT")
            {
                conMan.RestoreToOriginal();
                return 1;
            }
            std::vector<mbint> ints;
            if(UnpackArgs(arguments, std::back_inserter(ints)) && ints.size() == 2)
            {
                COORD dims = {static_cast<SHORT>(ints[0]), static_cast<SHORT>(ints[1])};
                if(!conMan.ResizeWindow(dims))
                {
                    error = INTERNAL_FUNCTION_FAILURE;
                    return static_cast<mbint>(GetLastError());
                }
                return 1;
            }
            else
            {
                error = INVALID_PARAMETER;
            }
            return 0;
        }

        mbint Color(Environment* env, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<mbint> ints;
            if(UnpackArgs(arguments, std::back_inserter(ints)) && (ints.size() == 2))
            {
                if((ints[0] >= 0 && ints[0] < 16) && (ints[1] >= 0 && ints[1] < 16))
                {
                    WORD attribute = static_cast<WORD>(ints[0]) | static_cast<WORD>(ints[1] << 4);
                    ConsoleManager& conMan = env->GetConsole();
                    if(!conMan.ChangeCurrentAttributes(attribute))
                    {
                        error = INTERNAL_FUNCTION_FAILURE;
                        return static_cast<mbint>(GetLastError());
                    }
                }
                else
                {
                    error = INVALID_PARAMETER;
                }
            }
            else
            {
                error = INVALID_PARAMETER;
            }
            return 0;
        }

        namespace USR
        {
            const void* ConvertArgs(const std::string& stringArg)
            {
                try
                {
                    return reinterpret_cast<const void*>(boost::lexical_cast<mbint>(stringArg));
                }
                catch(const boost::bad_lexical_cast&)
                {
                    return reinterpret_cast<const void*>(stringArg.c_str());
                }
            }

#ifndef _WIN64 // assumes 32-bittedness, you're on your own for 64-bit
            mbint CallFunc(void* func, const void** lastArg, size_t numArgs)
            {
                mbint returnValue = 0;
                void* stack;
                __asm mov stack, esp;
                // save the stack pointer somewhere not on the stack
                // in case things go really tits up
                TlsSetValue(tlsSlotIndex, stack);
                __try
                {
                    __asm
                    {
                        push eax;
                        push edi;
                        push ecx;
                        mov edi, esp;
                        mov ecx, lastArg;
                        mov eax, numArgs;

            loopStart:
                        cmp eax, 0;
                        jz callFunction;
                        push [ecx];
                        sub ecx, SIZE lastArg;
                        dec eax;
                        jmp loopStart;

            callFunction:
                        call func; assumed to be stdcall
                        mov returnValue, eax;
                        mov esp, edi;
                        pop ecx;
                        pop edi;
                        pop eax;
                    }
                }
                __except(EXCEPTION_EXECUTE_HANDLER)
                {
                    stack = TlsGetValue(tlsSlotIndex);
                    __asm mov esp, stack;
                }
                return returnValue;
            }
        }
#endif

        mbint Usr(Environment*, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<std::string> splitStrings;
            mbint returnValue = 0;
            ExplodeDelimitedString(arguments, ';', std::back_inserter(splitStrings));
            if(splitStrings.size() >= 2)
            {
                ACTCTXA dllContext = {sizeof(ACTCTXA), 0};
                dllContext.lpSource = splitStrings[0].c_str();
                ULONG_PTR actCookie = 0;
                BOOL deActivate = FALSE;
                HANDLE hCtx = CreateActCtxA(&dllContext);
                if(hCtx == INVALID_HANDLE_VALUE)
                {
                    hCtx = NULL;
                }
                else
                {
                    deActivate = ActivateActCtx(hCtx, &actCookie);
                }
                HMODULE hMod = LoadLibraryA(splitStrings[0].c_str());
                if(hMod)
                {
                    void* funAddress = reinterpret_cast<void*>(GetProcAddress(hMod, splitStrings[1].c_str()));
                    if(funAddress)
                    {
                        std::vector<const void*> funArgs(splitStrings.size() - 2);
                        std::transform(splitStrings.begin() + 2, splitStrings.end(), funArgs.begin(), &USR::ConvertArgs);
                        size_t numArgs = funArgs.size();
                        returnValue = USR::CallFunc(funAddress, numArgs ? &funArgs.back() : 0, numArgs);
                    }
                    else
                    {
                        error = USR_FUNCTION_DOESNT_EXIST;
                    }
                    FreeLibrary(hMod);
                }
                else
                {
                    error = USR_DLL_DOESNT_EXIST;
                }
                if(hCtx)
                {
                    if(deActivate)
                    {
                        DeactivateActCtx(0, actCookie);
                    }
                    ReleaseActCtx(hCtx);
                }
            }
            else
            {
                error = INVALID_PARAMETER; // not enough arguments
            }
            return returnValue;
        }

        mbint CLS(Environment* env, const std::string&, ERROR_CONDITION& error)
        {
            if(!env->GetConsole().Clear())
            {
                error = INTERNAL_FUNCTION_FAILURE;
                return static_cast<mbint>(GetLastError());
            }
            return 0;
        }

        mbint Rnd(Environment*, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<mbint> ints;
            if(UnpackArgs(arguments, std::back_inserter(ints)) && ints.size() == 1)
            {
                return (rand() % ints[0]) + 1;
            }
            error = INVALID_PARAMETER;
            return 0;
        }

        mbint Abs(Environment*, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<mbint> ints;
            if(UnpackArgs(arguments, std::back_inserter(ints)) && (ints.size() == 1))
            {
                return abs(ints[0]);
            }
            else
            {
                error = INVALID_PARAMETER;
            }
            return 0;
        }

        mbint Wait(Environment*, const std::string& arguments, ERROR_CONDITION& error)
        {
            std::vector<mbint> ints;
            if(UnpackArgs(arguments, std::back_inserter(ints)) && (ints.size() == 1))
            {
                Sleep(ints[0]);
                return 1;
            }
            else
            {
                error = INVALID_PARAMETER;
            }
            return 0;
        }

        // non-parenthesised function calls
        mbint Size(Environment* env)
        {
            return env->GetUserMemoryArea().Size();
        }

        mbint Get(Environment* env, const std::string&, ERROR_CONDITION& error)
        {
            mbint key = env->GetConsole().GetKey();
            if(!key)
            {
                error = INTERNAL_FUNCTION_FAILURE;
                return static_cast<mbint>(GetLastError());
            }
            return key;
        }

        mbint BeepIt(Environment*)
        {
            return Beep(440, 250);
        }
    }
}
